#include "Matrix.h"
#include <string.h>
#include <stdio.h>
#include <math.h>

#define MATRIX_PI_ 3.14159265358979323846

/// The Common interface for Matrix 
// Create an default Matrix
Matrix::Matrix()
{
    Reset();
};
    
void Matrix::Reset()
{
    memset(value, 0, sizeof(value));
    value[0] = 1;
    value[5] = 1;
    value[10] = 1;
    value[15] = 1;
};
    
// multiply a matrix
void Matrix::Multiply(const Matrix &a)
{
    float newValue[16];

    newValue[0]  = value[0] * a.value[0]  + value[4] * a.value[1]  + value[8] * a.value[2]   + value[12] * a.value[3];
    newValue[1]  = value[1] * a.value[0]  + value[5] * a.value[1]  + value[9] * a.value[2]   + value[13] * a.value[3];
    newValue[2]  = value[2] * a.value[0]  + value[6] * a.value[1]  + value[10] * a.value[2]  + value[14] * a.value[3];
    newValue[3]  = value[3] * a.value[0]  + value[7] * a.value[1]  + value[11] * a.value[2]  + value[15] * a.value[3];

    newValue[4]  = value[0] * a.value[4]  + value[4] * a.value[5]  + value[8] * a.value[6]   + value[12] * a.value[7];
    newValue[5]  = value[1] * a.value[4]  + value[5] * a.value[5]  + value[9] * a.value[6]   + value[13] * a.value[7];
    newValue[6]  = value[2] * a.value[4]  + value[6] * a.value[5]  + value[10] * a.value[6]  + value[14] * a.value[7];
    newValue[7]  = value[3] * a.value[4]  + value[7] * a.value[5]  + value[11] * a.value[6]  + value[15] * a.value[7];

    newValue[8]  = value[0] * a.value[8]  + value[4] * a.value[9]  + value[8] * a.value[10]  + value[12] * a.value[11];
    newValue[9]  = value[1] * a.value[8]  + value[5] * a.value[9]  + value[9] * a.value[10]  + value[13] * a.value[11];
    newValue[10] = value[2] * a.value[8]  + value[6] * a.value[9]  + value[10] * a.value[10] + value[14] * a.value[11];
    newValue[11] = value[3] * a.value[8]  + value[7] * a.value[9]  + value[11] * a.value[10] + value[15] * a.value[11];

    newValue[12] = value[0] * a.value[12] + value[4] * a.value[13] + value[8] * a.value[14]  + value[12] * a.value[15];
    newValue[13] = value[1] * a.value[12] + value[5] * a.value[13] + value[9] * a.value[14]  + value[13] * a.value[15];
    newValue[14] = value[2] * a.value[12] + value[6] * a.value[13] + value[10] * a.value[14] + value[14] * a.value[15];
    newValue[15] = value[3] * a.value[12] + value[7] * a.value[13] + value[11] * a.value[14] + value[15] * a.value[15];    
    
    memcpy(value, newValue, sizeof(value));
};

// add a matrix
void Matrix::Add(const Matrix &a)
{
    value[0] += a.value[0];
    value[1] += a.value[1];
    value[2] += a.value[2];
    value[3] += a.value[3];
    value[4] += a.value[4];
    value[5] += a.value[5];
    value[6] += a.value[6];
    value[7] += a.value[7];
    value[8] += a.value[8];
    value[9] += a.value[9];
    value[10] += a.value[10];
    value[11] += a.value[11];
    value[12] += a.value[12];
    value[13] += a.value[13];
    value[14] += a.value[14];
    value[15] += a.value[15];
};

// rotate a transform matrix arround x
void Matrix::RotateX(float alpha)
{
    alpha = alpha*MATRIX_PI_/180;
    float cosAlpha = cosf(alpha);
    float sinAlpha = sinf(alpha);
    Matrix a;
    a.value[5] = cosAlpha;
    a.value[6] = sinAlpha;
    a.value[9] = -sinAlpha;
    a.value[10] = cosAlpha;
    Multiply(a);
};

// rotate a transform matrix arround y
void Matrix::RotateY(float alpha)
{
    alpha = alpha*MATRIX_PI_/180;
    float cosAlpha = cosf(alpha);
    float sinAlpha = sinf(alpha);
    Matrix a;
    a.value[0] = cosAlpha;
    a.value[2] = -sinAlpha;
    a.value[8] = sinAlpha;
    a.value[10] = cosAlpha;
    Multiply(a);
};

// rotate a transform matrix arround z
void Matrix::RotateZ(float alpha)
{
    alpha = alpha*MATRIX_PI_/180;
    float cosAlpha = cosf(alpha);
    float sinAlpha = sinf(alpha);
    Matrix a;
    a.value[0] = cosAlpha;
    a.value[1] = sinAlpha;
    a.value[4] = -sinAlpha;
    a.value[5] = cosAlpha;
    Multiply(a);    
};

// translate a transform matrix
void Matrix::Translate(float x, float y, float z)
{
    Matrix a;
    a.value[12] = x;
    a.value[13] = y;
    a.value[14] = z;
    Multiply(a);
};

// scale a transform matrix 
void Matrix::Scale(float x, float y, float z)
{
    Matrix a;
    a.value[0] = x;
    a.value[5] = y;
    a.value[10] = z;
    Multiply(a);
};

// Load OrthoGraphics to this Matrix
void Matrix::LoadOrthoGraphics(float left, float right, float bottom, float top, float near, float far)
{
    float r_l = right - left;
    float t_b = top - bottom;
    float f_n = far - near;
    float tx = - (right + left) / (right - left);
    float ty = - (top + bottom) / (top - bottom);
    float tz = - (far + near) / (far - near);

    memset(value, 0, sizeof(value));
    
    value[0] = 2.0f / r_l;
    value[5] = 2.0f / t_b;
    value[10] = -2.0f / f_n;
    value[12] = tx;
    value[13] = ty;
    value[14] = tz;
    value[15] = 1.0f;

};
  
// set Perpertive
void Matrix::LoadPerperstive(float fovy, float aspect, float zNear, float zFar)
{
    memset(value, 0, sizeof(value));
    fovy = fovy * PI_NUMBER / 180;
    float f = 1.0f / tanf(fovy / 2.0f);
    value[0] = f / aspect;

    value[5] = f;
    value[10] = (zFar + zNear) / (zNear - zFar);    
    value[11] = -1.0f;
    value[14] = 2 * zFar * zNear /  (zNear - zFar);
};

void Matrix::Print()
{
    for (int i = 0; i < 16; i++)
    {
        printf("%10.5f, ", value[i]);
    };
    printf("\n");
};


// Multiply operator
Matrix Matrix::operator*(const Matrix &matrix)
{
    Matrix result(*this);
    return result *= matrix;
}

// Multiply operator
Matrix &Matrix::operator*=(const Matrix &matrix)
{
    Multiply(matrix);
    return *this;
}

